System.Span<T> estrutura

Observação

Este artigo fornece observações complementares à documentação de referência para esta API.

O Span<T> tipo é um ref struct alocado na pilha em vez de no heap gerenciado. Os tipos Ref struct têm uma série de restrições para garantir que não possam ser promovidos para o heap gerenciado, como não podem ser encaixotados, não podem ser atribuídos a variáveis do tipo Object ou dynamic nem a qualquer tipo de interface, não podem ser campos em um tipo de referência e não podem ser utilizados em limites de await e yield. Além disso, chamadas para dois métodos, Equals(Object) e GetHashCode, lançam um NotSupportedException.

Importante

Por ser um tipo somente de pilha, Span<T> não é adequado para muitos cenários que exigem o armazenamento de referências a buffers no heap. Isso é verdade, por exemplo, para rotinas que fazem chamadas de método assíncronas. Para esses cenários, pode-se usar os tipos System.Memory<T> e System.ReadOnlyMemory<T> complementares.

Para intervalos que representam estruturas imutáveis ou apenas de leitura, use System.ReadOnlySpan<T>.

Memória

A Span<T> representa uma região contígua de memória arbitrária. Uma Span<T> instância é frequentemente usada para armazenar os elementos de uma matriz ou de uma parte de uma matriz. Ao contrário de uma matriz, no entanto, uma instância Span<T> pode referir-se a memória gerida, memória nativa ou memória gerida na pilha. O exemplo a seguir cria um Span<Byte> a partir de uma matriz:

// Create a span over an array.
byte[] array = new byte[100];
Span<byte> arraySpan = new(array);

byte data = 0;
for (int ctr = 0; ctr < arraySpan.Length; ctr++)
    arraySpan[ctr] = data++;

int arraySum = 0;
foreach (byte value in array)
    arraySum += value;

Console.WriteLine($"The sum is {arraySum}");

// Output:  The sum is 4950
// Create a span over an array.
let array = Array.zeroCreate<byte> 100
let arraySpan = Span<byte> array

let mutable data = 0uy
for i = 0 to arraySpan.Length - 1 do
    arraySpan[i] <- data
    data <- data + 1uy

let mutable arraySum = 0
for value in array do
    arraySum <- arraySum + int value

printfn $"The sum is {arraySum}"
// Output:  The sum is 4950

O exemplo a seguir cria um Span<Byte> a partir de 100 bytes de memória nativa:

// Create a span from native memory.
nint native = Marshal.AllocHGlobal(100);
Span<byte> nativeSpan;
unsafe
{
    nativeSpan = new Span<byte>(native.ToPointer(), 100);
}
byte data = 0;
for (int ctr = 0; ctr < nativeSpan.Length; ctr++)
    nativeSpan[ctr] = data++;

int nativeSum = 0;
foreach (byte value in nativeSpan)
    nativeSum += value;

Console.WriteLine($"The sum is {nativeSum}");
Marshal.FreeHGlobal(native);

// Output:  The sum is 4950
// Create a span from native memory.
let native = Marshal.AllocHGlobal 100
let nativeSpan = Span<byte>(native.ToPointer(), 100)

let mutable data = 0uy
for i = 0 to nativeSpan.Length - 1 do
    nativeSpan[i] <- data
    data <- data + 1uy

let mutable nativeSum = 0
for value in nativeSpan do
    nativeSum <- nativeSum + int value

printfn $"The sum is {nativeSum}"
Marshal.FreeHGlobal native
// Output:  The sum is 4950

O exemplo a seguir usa a palavra-chave stackalloc do C# para alocar 100 bytes de memória na pilha:

// Create a span on the stack.
byte data = 0;
Span<byte> stackSpan = stackalloc byte[100];
for (int ctr = 0; ctr < stackSpan.Length; ctr++)
    stackSpan[ctr] = data++;

int stackSum = 0;
foreach (byte value in stackSpan)
    stackSum += value;

Console.WriteLine($"The sum is {stackSum}");

// Output:  The sum is 4950
    // Create a span on the stack.
    let mutable data = 0uy
    let stackSpan = 
        let p = NativeInterop.NativePtr.stackalloc<byte> 100 |> NativeInterop.NativePtr.toVoidPtr
        Span<byte>(p, 100)

    for i = 0 to stackSpan.Length - 1 do
        stackSpan[i] <- data
        data <- data + 1uy

    let mutable stackSum = 0
    for value in stackSpan do
        stackSum <- stackSum + int value

    printfn $"The sum is {stackSum}"
// Output:  The sum is 4950

Como Span<T> é uma abstração sobre um bloco arbitrário de memória, métodos Span<T> do tipo e métodos com Span<T> parâmetros operam em qualquer Span<T> objeto, independentemente do tipo de memória que ele encapsula. Por exemplo, cada uma das seções separadas do código que inicializam a extensão e calculam a soma de seus elementos pode ser refatorada em métodos únicos de inicialização e cálculo, como ilustra o exemplo a seguir:

public static void WorkWithSpans()
{
    // Create a span over an array.
    byte[] array = new byte[100];
    Span<byte> arraySpan = new(array);

    InitializeSpan(arraySpan);
    Console.WriteLine($"The sum is {ComputeSum(arraySpan):N0}");

    // Create an array from native memory.
    var native = Marshal.AllocHGlobal(100);
    Span<byte> nativeSpan;
    unsafe
    {
        nativeSpan = new Span<byte>(native.ToPointer(), 100);
    }

    InitializeSpan(nativeSpan);
    Console.WriteLine($"The sum is {ComputeSum(nativeSpan):N0}");

    Marshal.FreeHGlobal(native);

    // Create a span on the stack.
    Span<byte> stackSpan = stackalloc byte[100];

    InitializeSpan(stackSpan);
    Console.WriteLine($"The sum is {ComputeSum(stackSpan):N0}");
}

public static void InitializeSpan(Span<byte> span)
{
    byte value = 0;
    for (int ctr = 0; ctr < span.Length; ctr++)
        span[ctr] = value++;
}

public static int ComputeSum(ReadOnlySpan<byte> span)
{
    int sum = 0;
    foreach (byte value in span)
        sum += value;

    return sum;
}

// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950
open System
open System.Runtime.InteropServices
open FSharp.NativeInterop

// Package FSharp.NativeInterop.NativePtr.stackalloc for reuse.
let inline stackalloc<'a when 'a: unmanaged> length : Span<'a> =
    let voidPointer = NativePtr.stackalloc<'a> length |> NativePtr.toVoidPtr
    Span<'a>(voidPointer, length)

let initializeSpan (span: Span<byte>) =
    let mutable value = 0uy
    for i = 0 to span.Length - 1 do
        span[i] <- value
        value <- value + 1uy

let computeSum (span: Span<byte>) =
    let mutable sum = 0
    for value in span do
        sum <- sum + int value
    sum

let workWithSpans () =
    // Create a span over an array.
    let array = Array.zeroCreate<byte> 100
    let arraySpan = Span<byte> array

    initializeSpan arraySpan
    printfn $"The sum is {computeSum arraySpan:N0}"

    // Create an array from native memory.
    let native = Marshal.AllocHGlobal 100
    let nativeSpan = Span<byte>(native.ToPointer(), 100)

    initializeSpan nativeSpan
    printfn $"The sum is {computeSum nativeSpan:N0}"

    Marshal.FreeHGlobal native

    // Create a span on the stack.
    let stackSpan = stackalloc 100

    initializeSpan stackSpan
    printfn $"The sum is {computeSum stackSpan:N0}"

// The example displays the following output:
//    The sum is 4,950
//    The sum is 4,950
//    The sum is 4,950

Matrizes

Quando ele encapsula uma matriz, Span<T> pode envolver uma matriz inteira, como fez nos exemplos na seção Memória . Como ele suporta fatiamento, Span<T> também pode apontar para qualquer intervalo contíguo dentro da matriz.

O exemplo a seguir cria uma fatia dos cinco elementos do meio de uma matriz inteira de 10 elementos. Observe que o código dobra os valores de cada inteiro na fatia. Como mostra a saída, as alterações feitas pelo span são refletidas nos valores da matriz.

using System;

var array = new int[] { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
var slice = new Span<int>(array, 2, 5);
for (int ctr = 0; ctr < slice.Length; ctr++)
    slice[ctr] *= 2;

// Examine the original array values.
foreach (var value in array)
    Console.Write($"{value}  ");
Console.WriteLine();

// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20
module Program

open System

[<EntryPoint>]
let main _ =
    let array = [| 2; 4; 6; 8; 10; 12; 14; 16; 18; 20 |]
    let slice = Span<int>(array, 2, 5)
    for i = 0 to slice.Length - 1 do
        slice[i] <- slice[i] * 2

    // Examine the original array values.
    for value in array do
        printf $"{value}  "
    printfn ""
    0
// The example displays the following output:
//      2  4  12  16  20  24  28  16  18  20

Segmentações

Span<T> Inclui duas sobrecargas do método Slice que formam uma fatia da extensão atual que começa num índice especificado. Isso torna possível tratar os dados em um Span<T> como um conjunto de blocos lógicos que podem ser processados conforme necessário por partes de um pipeline de processamento de dados com impacto mínimo no desempenho. Por exemplo, como os protocolos de servidor modernos geralmente são baseados em texto, a manipulação de strings e substrings é particularmente importante. Na classe String, o método principal para extrair substrings é Substring. Para pipelines de dados que dependem de manipulação extensiva de strings, o seu uso pode ter algum impacto no desempenho, uma vez que:

  1. Cria uma nova cadeia de caracteres para armazenar a substring.
  2. Copia um subconjunto dos caracteres da cadeia de caracteres original para a nova cadeia de caracteres.

Essa operação de alocação e cópia pode ser eliminada usando um Span<T> ou ReadOnlySpan<T>, como mostra o exemplo a seguir:

using System;

class Program2
{
    static void Run()
    {
        string contentLength = "Content-Length: 132";
        var length = GetContentLength(contentLength.ToCharArray());
        Console.WriteLine($"Content length: {length}");
    }

    private static int GetContentLength(ReadOnlySpan<char> span)
    {
        var slice = span.Slice(16);
        return int.Parse(slice);
    }
}
// Output:
//      Content length: 132
module Program2

open System

let getContentLength (span: ReadOnlySpan<char>) =
    let slice = span.Slice 16
    Int32.Parse slice

let contentLength = "Content-Length: 132"
let length = getContentLength (contentLength.ToCharArray())
printfn $"Content length: {length}"
// Output:
//      Content length: 132